package br.ufrn.btest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import br.ufrn.btest.decorators.expressions.MyAIntervalExpression;
import br.ufrn.btest.decorators.predicates.MyABelongPredicate;
import br.ufrn.btest.decorators.predicates.MyAIncludePredicate;
import br.ufrn.btest.decorators.predicates.MyAIncludeStrictlyPredicate;
import br.ufrn.btest.decorators.predicates.MyPredicate;


public class BlockBuilder {

	
	private static final int INTERVAL_TEST_MARGIN = 5;
	private Partitioner partitioner;
	

	
	public BlockBuilder(Partitioner partitioner) {
		this.partitioner = partitioner;
	}
	
	
	
	public Map<String, List<String>> getEquivalentClassesBlocks() {
		return arrangeEquivalentClassesBlocks();
	}
	
	
	
	private Map<String, List<String>> arrangeEquivalentClassesBlocks() {
		HashMap<String, List<String>> blocks = new HashMap<String, List<String>>();

		addGeneralCharacteristicsBlocks(blocks);
		addConditionalCharacteristicsBlocks(blocks);
		
		return blocks;
	}
	
	
	
	private void addGeneralCharacteristicsBlocks(HashMap<String, List<String>> blocks) {
		for (Characteristic characteristic : this.partitioner.getGeneralCharacteristicsClauses()) {
			if(characteristic.getPredicate().isInterval()) {
				addIntervalBlock(blocks, characteristic);
			} else {
				addCommonBlock(blocks, characteristic);
			}
		}
	}
	
	
	
	private void addIntervalBlock(HashMap<String, List<String>> blocks, Characteristic characteristic) {
		blocks.put(characteristic.getPredicate().toString(), getIntervalBlocks(characteristic.getPredicate()));
	}
	
	
	
	private List<String> getIntervalBlocks(MyPredicate characteristic) {
		List<String> intervalBlocks = new ArrayList<String>();
		
		if (characteristic instanceof MyABelongPredicate) {
			addIntervalBlocksToBelongs(characteristic, intervalBlocks);
		} else if(characteristic instanceof MyAIncludePredicate) {
			addIntervalBlocksToIncludes(characteristic, intervalBlocks);
		} else if(characteristic instanceof MyAIncludeStrictlyPredicate) {
			addIntervalBlocksToIncludesStrictly(characteristic, intervalBlocks);
		}
		
		return intervalBlocks;
	}

	
	
	private void addIntervalBlocksToBelongs(MyPredicate characteristic, List<String> intervalBlocks) {
		MyABelongPredicate belong = (MyABelongPredicate) characteristic;
		
		if(belong.getRightExpression().isInterval()) {
			MyAIntervalExpression interval = (MyAIntervalExpression) belong.getRightExpression();
			String leftTerm = belong.getLeftExpression().toString();
			buildIntervals(intervalBlocks, leftTerm, " : ", interval);
		}
	}
	
	
	
	private void addIntervalBlocksToIncludes(MyPredicate characteristic, List<String> intervalBlocks) {
		MyAIncludePredicate include = (MyAIncludePredicate) characteristic;
		
		if(include.getRightExpression().isInterval()) {
			MyAIntervalExpression interval = (MyAIntervalExpression) include.getRightExpression();
			String leftTerm = include.getLeftExpression().toString();
			buildIntervals(intervalBlocks, leftTerm, " <: ", interval);
		}
	}
	
	
	
	private void addIntervalBlocksToIncludesStrictly(MyPredicate characteristic, List<String> intervalBlocks) {
		MyAIncludeStrictlyPredicate includeStrictly = (MyAIncludeStrictlyPredicate) characteristic;

		if(includeStrictly.getRightExpression().isInterval()) {
			MyAIntervalExpression interval = (MyAIntervalExpression) includeStrictly.getRightExpression();
			String leftTerm = includeStrictly.getLeftExpression().toString();
			buildIntervals(intervalBlocks, leftTerm, " <<: ", interval);
		}
	}
	
	
	
	private void buildIntervals(List<String> intervalBlocks, String leftTerm, String predicateSymbol, MyAIntervalExpression interval) {
		int leftBorder = Integer.valueOf(interval.getLeftExpression().toString());
		int rightBorder = Integer.valueOf(interval.getRightExpression().toString());
		
		int leftBorderReduced = leftBorder - INTERVAL_TEST_MARGIN;
		int rightBorderRaised = rightBorder + INTERVAL_TEST_MARGIN;
		
		intervalBlocks.add(leftTerm + predicateSymbol + leftBorderReduced + ".." + (leftBorder - 1));
		intervalBlocks.add(leftTerm + predicateSymbol + (rightBorder + 1) + ".." + rightBorderRaised);
		intervalBlocks.add(leftTerm + predicateSymbol + leftBorder + ".." + rightBorder);
	}
	
	
	
	
	private void addCommonBlock(HashMap<String, List<String>> blocks, Characteristic characteristic) {
		List<String> chBlocks = new ArrayList<String>();
		
		chBlocks.add(characteristic.toString());
		if(characteristic.getType() != CharacteristicType.INVARIANT && !characteristic.getPredicate().isTypingClause()) {
			chBlocks.add(negateClause(characteristic.getPredicate().toString()));
		}
		
		blocks.put(characteristic.getPredicate().toString(), chBlocks);
	}
	
	
	
	private void addConditionalCharacteristicsBlocks(HashMap<String, List<String>> blocks) {
		for (String conditional : this.partitioner.getOperation().getConditionalCharacteristics()) {
			List<String> chBlocks = new ArrayList<String>();
			chBlocks.add(conditional);
			chBlocks.add(negateClause(conditional));
			
			blocks.put(conditional, chBlocks);
		}
	}

	
	
	public Map<String, List<String>> getBoundaryValueBlocks() {
		return arrangeBoundaryBlocks();
	}
	
	
	
	private Map<String, List<String>> arrangeBoundaryBlocks() {
		HashMap<String, List<String>> blocks = new HashMap<String, List<String>>();

		addGeneralCharacteristicsBlocksUsingBoundaryAnalisys(blocks);
		addConditionalCharacteristicsBlocks(blocks);
		
		return blocks;
	}
	
	
	
	private void addGeneralCharacteristicsBlocksUsingBoundaryAnalisys(HashMap<String, List<String>> blocks) {
		for (Characteristic characteristic : this.partitioner.getGeneralCharacteristicsClauses()) {
			if(characteristic.getPredicate().isInterval()) {
				addIntervalBoundaryBlock(blocks, characteristic);
			} else {
				addCommonBlock(blocks, characteristic);
			}
		}
	}
	
	
	
	private void addIntervalBoundaryBlock(HashMap<String, List<String>> blocks, Characteristic characteristic) {
		blocks.put(characteristic.getPredicate().toString(), getBoundaryBlocks(characteristic.getPredicate()));
	}
	
	
	
	private List<String> getBoundaryBlocks(MyPredicate characteristic) {
		List<String> intervalBlocks = new ArrayList<String>();
		
		if (characteristic instanceof MyABelongPredicate) {
			addIntervalBoundaryBlocksToBelongs(characteristic, intervalBlocks);
		} else if(characteristic instanceof MyAIncludePredicate) {
			addIntervalBoundaryBlocksToIncludes(characteristic, intervalBlocks);
		} else if(characteristic instanceof MyAIncludeStrictlyPredicate) {
			addIntervalBoundaryBlocksToIncludesStrictly(characteristic, intervalBlocks);
		}
		
		return intervalBlocks;
	}
	
	
	
	private void addIntervalBoundaryBlocksToBelongs(MyPredicate characteristic, List<String> intervalBlocks) {
		MyABelongPredicate belong = (MyABelongPredicate) characteristic;
		
		if(belong.getRightExpression().isInterval()) {
			MyAIntervalExpression interval = (MyAIntervalExpression) belong.getRightExpression();
			String leftTerm = belong.getLeftExpression().toString();
			buildBoundaryIntervalsToBelongs(intervalBlocks, leftTerm, " : ", interval);
		}
	}
	
	
	
	private void addIntervalBoundaryBlocksToIncludes(MyPredicate characteristic, List<String> intervalBlocks) {
		MyAIncludePredicate include = (MyAIncludePredicate) characteristic;
		
		if(include.getRightExpression().isInterval()) {
			MyAIntervalExpression interval = (MyAIntervalExpression) include.getRightExpression();
			String leftTerm = include.getLeftExpression().toString();
			buildBoundaryIntervalsToIncludes(intervalBlocks, leftTerm, " <: ", interval);
		}
	}
	
	
	
	private void addIntervalBoundaryBlocksToIncludesStrictly(MyPredicate characteristic, List<String> intervalBlocks) {
		MyAIncludeStrictlyPredicate includeStrictly = (MyAIncludeStrictlyPredicate) characteristic;

		if(includeStrictly.getRightExpression().isInterval()) {
			MyAIntervalExpression interval = (MyAIntervalExpression) includeStrictly.getRightExpression();
			String leftTerm = includeStrictly.getLeftExpression().toString();
			buildBoundaryIntervalsToIncludes(intervalBlocks, leftTerm, " <<: ", interval);
		}
	}
	
	
	
	private void buildBoundaryIntervalsToBelongs(List<String> intervalBlocks, String leftTerm, String predicateSymbol, MyAIntervalExpression interval) {
		int leftBorder = Integer.valueOf(interval.getLeftExpression().toString());
		int rightBorder = Integer.valueOf(interval.getRightExpression().toString());
		
		int inferiorLeftBorder = leftBorder - 1;
		int superiorLeftBorder = leftBorder + 1;
		int inferiorRightBorder = rightBorder - 1;
		int superiorRightBorder = rightBorder + 1;
		
		intervalBlocks.add(leftTerm + " = " + inferiorLeftBorder);
		intervalBlocks.add(leftTerm + " = " + superiorLeftBorder);
		intervalBlocks.add(leftTerm + " = " + inferiorRightBorder);
		intervalBlocks.add(leftTerm + " = " + superiorRightBorder);
	}
	
	
	
	private void buildBoundaryIntervalsToIncludes(List<String> intervalBlocks, String leftTerm, String predicateSymbol, MyAIntervalExpression interval) {
		int leftBorder = Integer.valueOf(interval.getLeftExpression().toString());
		int rightBorder = Integer.valueOf(interval.getRightExpression().toString());
		
		int inferiorLeftBorder = leftBorder - 1;
		int superiorLeftBorder = leftBorder + 1;
		int inferiorRightBorder = rightBorder - 1;
		int superiorRightBorder = rightBorder + 1;
		
		intervalBlocks.add(leftTerm + " = " + "{" + inferiorLeftBorder + "}");
		intervalBlocks.add(leftTerm + " = " + "{" + superiorLeftBorder + "}");
		intervalBlocks.add(leftTerm + " = " + "{" + inferiorRightBorder + "}");
		intervalBlocks.add(leftTerm + " = " + "{" + superiorRightBorder + "}");
	}
	


	public List<List<String>> getEquivalentClassesBlocksAsLists() {
		return parseMapToList(getEquivalentClassesBlocks());
	}
	
	
	
	private List<List<String>> parseMapToList(Map<String, List<String>> partitionBlocks) {
		List<List<String>> parameterInputValues = new ArrayList<List<String>>();

		Set<String> keySet = partitionBlocks.keySet();
		for (String key : keySet) {
			parameterInputValues.add(partitionBlocks.get(key));
		}
		
		return parameterInputValues;
	}
	
	
	
	private String negateClause(String clause) {
		return "not(" + clause + ")";
	}

}
